import java.util.ArrayList;
import java.util.List;
import java.util.Random;
import java.util.concurrent.Callable;
import java.util.concurrent.ConcurrentLinkedQueue;
import javax.swing.SwingWorker;



public class CombatEngine extends SwingWorker<Player, Callable<Void>> {
	
	static int newID = 0;
	Player savedPlayer;
	public Player currentPlayer;
	public AI nextPlayer;
	public Player whoLost;
	public boolean paused = false;
	public boolean inCombat = true;
	public int aniAIFinished = 0;
	public int aniPlayerFinished = 0;
	public int turnTick = 0;
	public int aniTime = 0;
	protected boolean flee = false;
	public boolean catchAble = true;
	public boolean fånga = false;
	public boolean usedPassive = false;
	private MapNode currentNode;
	// en kopia av fiendens djur
	Pet enemyPet; 

	CombatSkeleton cs;
	ConcurrentLinkedQueue<Action> actionQueue;
	
	ArrayList<Pet> deadPets = new ArrayList<Pet>();

	

	public CombatEngine(Player realPlayer, AI enemy, CombatSkeleton cs, ConcurrentLinkedQueue<Action> actionQueue, boolean catchAble, MapNode currentNode){
		
	    currentPlayer = realPlayer;
		savedPlayer = realPlayer.copy();
		nextPlayer = enemy;
		enemyPet = enemy.activePet.copy();
		
		this.actionQueue = actionQueue;
		this.cs = cs;		
		this.catchAble = catchAble;
		this.currentNode = currentNode;
		currentPlayer.activePet.energy = 0;
	}
	

	@Override
	protected Player doInBackground() throws Exception {
		/*
		 * doInBackground() sköter striden, den ber turnhandler att exekvera actions i kön
		 * och testar om striden är över
		 */
		combatBegin();

		while (inCombat){
			
			long before = System.nanoTime(); // FPS counting
			turnTick++;
			Action nextAction = actionQueue.poll();
			inCombat = turnHandler(nextAction, currentPlayer, nextPlayer);
			
			if (inCombat == true){
				nextAction = nextPlayer.doSomething(currentPlayer.activePet, currentPlayer.difficulty, catchAble);
				inCombat = turnHandler(nextAction, nextPlayer, currentPlayer);
			}
			try {
				Thread.sleep(16);
			} catch (InterruptedException e) {
				throw new RuntimeException(e);
			}
			
			long after = System.nanoTime(); // FPS counting
			@SuppressWarnings("unused")
			double elapsed = (after - before)/1000000.0; // FPS counting
			// FPS counting: System.out.println("Elapsed time for turn: " + Double.toString(elapsed) + " ms, " + Integer.toString((int) (1000/elapsed)) + " FPS.");
		}
		
		combatEnd();
		
		return null;
	}
	
	private void update() {
		publish(new Callable<Void>() { public Void call() {
			cs.update();
			return null;}});
	}
	private void displayAction(final String str) {
		publish(new Callable<Void>() { public Void call() {
			cs.displayAction(str);
			return null;}});
	}
	private void closePopupMenus() {
		publish(new Callable<Void>() { public Void call() {
			cs.closePopupMenus();
			return null;}});
	}
	private void runPlayerItemAnimation(final String ani, final int anitime) {
		publish(new Callable<Void>() { public Void call() {
			cs.runPlayerItemAnimation(ani, anitime);
			return null;}});
	}
	private void runEnemyItemAnimation(final String ani, final int anitime) {
		publish(new Callable<Void>() { public Void call() {
			cs.runEnemyItemAnimation(ani, anitime);
			return null;}});
	}
	private void runPlayerAttackAnimation(final String ani, final int anitime) {
		publish(new Callable<Void>() { public Void call() {
			cs.runPlayerAttackAnimation(ani, anitime);
			return null;}});
	}
	private void runEnemyAttackAnimation(final String ani, final int anitime) {
		publish(new Callable<Void>() { public Void call() {
			cs.runEnemyAttackAnimation(ani, anitime);
			return null;}});
	}
	
	@Override
	protected void process(List<Callable<Void>> messages) {
		for (Callable<Void> message : messages) {
			try {
				message.call();
			} catch (Exception e) {
				e.printStackTrace();
			}
		}
	}

		
	public void combatBegin() {
		if(nextPlayer.playerName != "Vilding"){
		displayAction(nextPlayer.playerName + " har " + nextPlayer.getLivingPets() + " husdjur.");
		displayAction("Gör ditt drag!");
		}
		else{
			displayAction("En vilsen " + nextPlayer.activePet.name + " står i din väg!");
		    displayAction("Vad vill du göra?");
		}
		
	}
	
	public void combatEnd(){
		if (whoLost == nextPlayer){
			currentPlayer.money = currentPlayer.money + nextPlayer.money;
			if(nextPlayer.playerName != "Vilding"){
				displayAction("Du har besegrat " + nextPlayer.playerName + ".");
				}
				else{
					displayAction("Du har besegrat " + nextPlayer.activePet.name + ".");
					displayAction("Du tog " + nextPlayer.money + " guld från " + nextPlayer.activePet.name + "s medvetslösa kropp.");
				}
			
			/* Om det är en tränare man just mötte, sätt att man har vunnit
			 * mot tränaren i denna nod. */
			if (catchAble == false) {
				currentPlayer.defeatedTrainers.add(currentNode.index);
			}
			
			ArrayList<Pet> enemyPets = new ArrayList<Pet>();
			enemyPets.add(nextPlayer.activePet);
			if(nextPlayer.passivePet != null)
				enemyPets.add(nextPlayer.passivePet);
			enemyPets.addAll(nextPlayer.passivePetList);
			// Hur mycket XP man får baseras på genomsnittet av ens levels
			// (eftersom både lägsta och högsta suger. Förbättringar är välkomna...)
			int levelLimit = currentPlayer.activePet.getCurrentLevel();
			if (currentPlayer.passivePet != null) {
				levelLimit = (int) ((levelLimit + currentPlayer.passivePet.getCurrentLevel())/2.0 + 0.5);
			}
			int earnedXP = 0;
			// XP för fiendens pets
			for (Pet p : enemyPets) {
				if(p.getCurrentLevel() > levelLimit + 1)
					earnedXP += 48;
				else if (p.getCurrentLevel() == levelLimit + 1) {
					earnedXP += 25;
				} else if (p.getCurrentLevel() == levelLimit) {
					earnedXP += 17;
				} else if (p.getCurrentLevel() == levelLimit - 1) {
					earnedXP += 9;
				} else {
					earnedXP += 2;
				}
			}
			// Extra XP om man möter en tränare
			if (catchAble == false) {
				earnedXP += 14;
			}
			
			if (!usedPassive || currentPlayer.passivePet == null || !currentPlayer.passivePet.isAlive()) {
				
				cs.attack.setEnabled(false);
				cs.items.setEnabled(false);
				cs.swap.setEnabled(false);
				cs.flee.setEnabled(false);
				cs.frame.getContentPane().repaint();
				// ge currentplayer savedplayers pets så de har full hp etc
				currentPlayer.activePet = savedPlayer.activePet.copy();
				if(currentPlayer.passivePet != null){
					 currentPlayer.passivePet = savedPlayer.passivePet.copy();
					}
				
				for (int i = 0; i < earnedXP; i++) {
					boolean levelUp = currentPlayer.activePet.gainXP(1);
					update();
					if(levelUp == true){
						if(!cs.victoryMode)
							displayAction(currentPlayer.activePet.name + " har nått en ny nivå!");
						currentPlayer.activePet.levelingUp(currentPlayer.activePet.strength, currentPlayer.activePet.defense, currentPlayer.activePet.speed, currentPlayer.activePet.size);
						AttackName newAttack = currentPlayer.activePet.checkNewAttack(currentPlayer.activePet.getCurrentLevel(),currentPlayer.activePet.element);
						if(newAttack != null) {
							displayAction(currentPlayer.activePet.name + " har lärt sig en ny attack!");
							currentPlayer.spellBook.addItem(newAttack);
							currentPlayer.activePet.addAttack(newAttack);
							}
						}
					try {
						Thread.sleep(100);
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}
				}
				if(cs.victoryMode){
					savedPlayer.activePet.currentXP = currentPlayer.activePet.currentXP; 
					cs.items.setEnabled(false);
					cs.attack.setEnabled(false);
					cs.swap.setText("Huvudmenyn");
					cs.flee.setText("Tillbaka");
					cs.swap.setEnabled(true);
					cs.flee.setEnabled(true);
					cs.startCredits();
				}
				else{
					displayAction(currentPlayer.activePet.name + " fick " + earnedXP + " erfarenhet.");
					savedPlayer.activePet.currentXP = currentPlayer.activePet.currentXP; 
					cs.flee.setVisible(false);
					cs.items.setEnabled(false);
					cs.attack.setEnabled(false);
					cs.swap.setEnabled(false);
					cs.add(cs.map, new Integer(2));
					cs.repaint();
				}
			} 
			else {
				cs.attack.setEnabled(false);
				cs.items.setEnabled(false);
				cs.swap.setEnabled(false);
				cs.flee.setEnabled(false);
				cs.frame.getContentPane().repaint();
				// ge currentplayer savedplayers pets så de har full hp etc
				currentPlayer.activePet = savedPlayer.activePet.copy();
				if(currentPlayer.passivePet != null){
					 currentPlayer.passivePet = savedPlayer.passivePet.copy();
					}
			
				for (int i = 0; i < earnedXP/2; i++) {
					boolean levelUp = currentPlayer.activePet.gainXP(1);
					update();
					if(levelUp == true){
						if(!cs.victoryMode)
							displayAction(currentPlayer.activePet.name + " har nått en ny nivå.");
						currentPlayer.activePet.levelingUp(currentPlayer.activePet.strength, currentPlayer.activePet.defense, currentPlayer.activePet.speed, currentPlayer.activePet.size);
						AttackName newAttack = currentPlayer.activePet.checkNewAttack(currentPlayer.activePet.getCurrentLevel(),currentPlayer.activePet.element);
						if(newAttack != null) {
							displayAction(currentPlayer.activePet.name + " har lärt en sig en ny attack!");
							currentPlayer.spellBook.addItem(newAttack);
							currentPlayer.activePet.addAttack(newAttack);
						}
					}
					try {
						Thread.sleep(200);
					} catch (InterruptedException e) {
						throw new RuntimeException(e);
					}
				}
				for (int i = 0; i < earnedXP/2; i++) {
					boolean levelUp = currentPlayer.passivePet.gainXP(1);
					update();
					if(levelUp == true){
						if(!cs.victoryMode)
							displayAction(currentPlayer.passivePet.name + " har nått en ny nivå.");
						currentPlayer.passivePet.levelingUp(currentPlayer.passivePet.strength, currentPlayer.passivePet.defense, currentPlayer.passivePet.speed, currentPlayer.passivePet.size);
						AttackName newAttack = currentPlayer.passivePet.checkNewAttack(currentPlayer.passivePet.getCurrentLevel(),currentPlayer.passivePet.element);
						if(newAttack != null) {
							displayAction(currentPlayer.passivePet.name + " har lärt sig en ny attack!");
							currentPlayer.spellBook.addItem(newAttack);
							currentPlayer.passivePet.addAttack(newAttack);
						}
					}
				}
				if(!cs.victoryMode){
					displayAction(currentPlayer.activePet.name + " fick " + earnedXP/2.0 + " erfarenhet.");
					displayAction(currentPlayer.passivePet.name + " fick " + earnedXP/2.0 + " erfarenhet.");
				}
				savedPlayer.activePet.currentXP = currentPlayer.activePet.currentXP; 
				savedPlayer.passivePet.currentXP = currentPlayer.passivePet.currentXP; 
				
				if(cs.victoryMode){
					cs.items.setEnabled(false);
					cs.attack.setEnabled(false);
					cs.swap.setText("Huvudmenyn");
					cs.flee.setText("Tillbaka");
					cs.swap.setEnabled(true);
					cs.flee.setEnabled(true);
					cs.startCredits();
					
				}
				else{
					cs.flee.setVisible(false);
					cs.items.setEnabled(false);
					cs.attack.setEnabled(false);
					cs.swap.setEnabled(false);
					cs.add(cs.map, new Integer(2));
					cs.repaint();
				}
				
			}
			
			for (ItemKind item : nextPlayer.inventory.getItems()) {
				int n = nextPlayer.inventory.itemCount(item);
				currentPlayer.inventory.addItem(item, n);
				displayAction("Du har fått nya föremål, kolla din ryggsäck!");
				
			}
			
		}

		if (whoLost == currentPlayer){
			displayAction("Dina husdjur övergav dig för att du misskötte dem!");
			displayAction("Spelet är över");
			cs.flee.setVisible(false);
			cs.items.setEnabled(false);
			cs.attack.setEnabled(false);
			cs.swap.setEnabled(false);
			cs.add(cs.mainmenu, new Integer(2));
			cs.repaint();
		}
		
		// Ville told me to do it! ;; johan
		else if(flee){
			// ge currentplayer savedplayers pets så de har full hp etc
			currentPlayer.activePet = savedPlayer.activePet.copy();
			if(currentPlayer.passivePet != null){
				 currentPlayer.passivePet = savedPlayer.passivePet.copy();
				}
			cs.map.doClick(1000);
			}
		else if(fånga){
			currentPlayer.activePet = savedPlayer.activePet.copy();
			if(currentPlayer.passivePet != null){
				 currentPlayer.passivePet = savedPlayer.passivePet.copy();
				}

			cs.catchMode = true;
			if(currentPlayer.passivePet == null){
				currentPlayer.passivePet = enemyPet.copy();
				try {
					Thread.sleep(2000);
				} catch (InterruptedException e) {
					throw new RuntimeException(e);
				}
				for(int i = 0; i < currentPlayer.passivePet.attacks.size(); i++)
					cs.player.spellBook.addItem(currentPlayer.passivePet.attacks.get(i));
				cs.frame.swapToNodeInterface(cs.player.nodeIndex, true);
				cs.frame.getContentPane().repaint();
			}
			else{
				displayAction("Du kan inte ha fler än två husdjur, vad vill du göra?");
				cs.flee.setVisible(false);
				cs.items.setEnabled(false);
				cs.attack.setEnabled(false);
				cs.swap.setEnabled(false);
				cs.add(cs.val, new Integer(2));
				cs.repaint();
				
			}
		}
	}

	
	
	private boolean turnHandler(Action nextAction, final Player activePlayer, final Player inactivePlayer) {
		
	
		// Detta måste ligga först!-------------------
		if (nextAction instanceof Pause){
			
			displayAction("Spelet pausat.");
			paused = !paused;
			return true;
			
		}
		
		if (paused){
			return true;
		}
		// ^ Detta måste ligga först!------------------
		
		if (!activePlayer.activePet.isAlive() && !deadPets.contains(activePlayer.activePet)){
			deadPets.add(activePlayer.activePet);
			if (activePlayer.passivePet == null || !activePlayer.passivePet.isAlive()){
				whoLost = activePlayer;
				return false;
			} 
			else {
			
				//displayAction("Åh nej! " + activePlayer.activePet.getName() + " blev allvarligt skadad!");
			}
			
			if (activePlayer == nextPlayer) {
					displayAction("Du har gjort " + activePlayer.activePet.getName() +" medvetslös.");
					displayAction(nextPlayer.playerName + " har " + nextPlayer.getLivingPets() + " husdjur.");
			}
			activePlayer.swapPet();
			if (!activePlayer.isAI) {
				savedPlayer.swapPet();
				closePopupMenus();
			}
		}
		
		if (activePlayer.activePet.energy < 100){
			activePlayer.activePet.increaseEnergy();
		}
		
		if (nextAction instanceof UseItem){
			
			Item usedItem = new Item(((UseItem) nextAction).itemIndex);

			if(activePlayer.activePet.energy < 10){
				
				if (!activePlayer.isAI) {
					closePopupMenus();
					displayAction("Inte tillräckligt med energi.");
				}
			} 
			else {
				
				if(usedItem.getName() == "Lakan"){
					
					if(catchAble == false)	
						displayAction("Du kan inte fånga husdjur som har en ägare!");
		
					else {
						displayAction(activePlayer.playerName + " använde Lakan");
						activePlayer.activePet.energy = activePlayer.activePet.energy - 10;
						activePlayer.inventory.removeItem(((UseItem) nextAction).itemIndex, 1);
						
						int prob = usedItem.effect.perform(activePlayer.activePet, inactivePlayer.activePet);
						if(prob == 0){
							displayAction("Lakan övertygade inte " + inactivePlayer.activePet.name + " att gå med dig");
						}
						else{
							displayAction("Du fångade " + inactivePlayer.activePet.name + ".");
							fånga = true;
							return false;
						}

						}
					return true;
					}

				if (usedItem.effect.relativeTarget == Target.Other && usedItem.getName() != "Lakan"){
					usedItem.effect.perform(activePlayer.activePet, inactivePlayer.activePet);
					displayAction(activePlayer.playerName + " använde " + usedItem.getName());
					activePlayer.activePet.energy = activePlayer.activePet.energy - 10;
					displayAction(usedItem.effect.getDescription());
					activePlayer.inventory.removeItem(((UseItem) nextAction).itemIndex, 1);
				}
				
				if (usedItem.effect.relativeTarget == Target.Self){
					usedItem.effect.perform(activePlayer.activePet, activePlayer.activePet);
					displayAction(activePlayer.playerName + " använde " + usedItem.getName());
					activePlayer.activePet.energy = activePlayer.activePet.energy - 10;
					displayAction(usedItem.effect.getDescription());
					activePlayer.inventory.removeItem(((UseItem) nextAction).itemIndex, 1);
				}

				if (activePlayer == currentPlayer){
					
					if (usedItem.effect.relativeTarget == Target.Self)
						runPlayerItemAnimation(usedItem.animation, usedItem.animationTime);
					else 
						runEnemyItemAnimation(usedItem.animation, usedItem.animationTime);
					
				}
				
				if (activePlayer == nextPlayer){
					if (usedItem.effect.relativeTarget == Target.Self) 
						runEnemyItemAnimation(usedItem.animation, usedItem.animationTime);
					
					else
						runPlayerItemAnimation(usedItem.animation, usedItem.animationTime);
				}

				}
			}
		

		
		
		if (nextAction instanceof UseAttack){
			
			
			Attack selectedAttack = new Attack(((UseAttack) nextAction).attackIndex);
			
			if (activePlayer.isAI == true){
				if (turnTick > aniAIFinished){
					activePlayer.aniPlaying = false;
					aniTime = selectedAttack.animationTime;
					aniAIFinished = turnTick + (int) ((aniTime/1000.0)*60.0);
				}
			}
			
			if (activePlayer.isAI == false){
				if (turnTick > aniPlayerFinished){
					activePlayer.aniPlaying = false;
					aniTime = selectedAttack.animationTime;
					aniPlayerFinished = turnTick + (int) ((aniTime/1000.0)*60.0);
				}
				else {
					displayAction("Attack pågår!");
				}
			}

			
			if (!activePlayer.aniPlaying){
				if((int) activePlayer.activePet.energy <= 100 && (activePlayer.activePet.energy >= selectedAttack.getEnergyCost()) ){
					activePlayer.activePet.energy = activePlayer.activePet.energy - selectedAttack.getEnergyCost(); //varför (int) activePlayer.activePet.energy <= 100
					
					displayAction(activePlayer.activePet.getName() + " använde " + selectedAttack.getName() + "!");
	
					if (selectedAttack.effects.relativeTarget == Target.Other){
						selectedAttack.effects.perform(activePlayer.activePet, inactivePlayer.activePet);
					}
					
					if (selectedAttack.effects.relativeTarget == Target.Self){
						selectedAttack.effects.perform(activePlayer.activePet, activePlayer.activePet);
						
					}
						
					if (activePlayer == currentPlayer){
						runPlayerAttackAnimation(selectedAttack.attackAnimation, selectedAttack.animationTime());
						activePlayer.aniPlaying = true;
				    
					}
					if (activePlayer == nextPlayer){
						runEnemyAttackAnimation(selectedAttack.attackReverseAnimation, selectedAttack.animationTime());
						activePlayer.aniPlaying = true;
					}
					displayAction(selectedAttack.effects.getDescription());
					
				}
				else {
					if (activePlayer.isAI == false){
						displayAction("Inte tillräckligt med energi!");
					}
				}
			}
		}
				
		
		if (nextAction instanceof SwapPet){
			
			if (activePlayer.activePet.energy >=20) {
				if (activePlayer.passivePet != null
					&& activePlayer.passivePet.isAlive()){
					activePlayer.swapPet();
					displayAction(activePlayer.playerName + " byter till " + activePlayer.activePet.getName() + "!");
					if (!activePlayer.isAI) {
						closePopupMenus();
						savedPlayer.swapPet();
						usedPassive = true;
					}
				} else if (!activePlayer.isAI) {
					displayAction("Du har ingen pet att byta till.");
				}
			}
			else {
				if (!activePlayer.isAI)
				displayAction("Du behöver 20 energy för att byta.");
			}	
		}
		
		if (nextAction instanceof Flee){
			
			double fly;
			double healthTest = activePlayer.activePet.getCurrentHealth()/(double) activePlayer.activePet.getMaxHealth();
			if(healthTest<=0.2){
				displayAction("Du kan inte fly när du har under 20% hälsa.");
			}
			else{
				if(activePlayer.activePet.energy >=10){
					activePlayer.activePet.energy = activePlayer.activePet.energy - 10;
					
					if(activePlayer.activePet.speed >= inactivePlayer.activePet.speed && healthTest >= 0.9)
					 	fly = 100;
					else
						fly = (((activePlayer.activePet.speed - inactivePlayer.activePet.speed) + (healthTest*100) + activePlayer.activePet.energy)/2.0);
				
					int randomInt = new Random().nextInt(99) + 1;
					if(fly>randomInt){
						displayAction("Du lyckades fly.");
						flee = true;
						return false;	
					}
					else {
						displayAction("Du lyckades inte fly.");
					}
				}
				else
					displayAction("10 energy krävs för att fly.");
			}
		}
		
		update();
		
		return true;
		/*
		 * Give next player control
		 */	
	}
}
